import { createMatrix, ReturnFunction } from "./createMatrix";
import { IsInRect } from "./IsInRect";
interface MatrixTemplate<T> {
    height: number;
    width: number;
    voidValue: T | ReturnFunction<T>;
    creator?: ReturnFunction<T>;
}
export interface Rect {
    minX: number;
    maxX: number;
    maxY: number;
    minY: number;
}

/**  二维数组类，用于操作矩阵的一些方法的集合 */
export default class Matrix<T = number> extends Array<T[]> {
    readonly height: number;
    readonly width: number;
    get rect(): Rect {
        return {
            minX: 0,
            minY: 0,
            maxX: this.width - 1,
            maxY: this.height - 1,
        };
    }
    get size() {
        return this.height * this.width;
    }
    constructor(input: MatrixTemplate<T> | T[][]) {
        super();
        if (input instanceof Array) {
            const height = input.length;
            const width = input[0].length;
            this.height = height;
            this.width = width;
            this.push(...input);
        } else {
            let { height, width, voidValue, creator } = input;
            this.height = height;
            this.width = width;
            const matrix = createMatrix<T>(height, width, voidValue, creator);
            this.push(...matrix);
        }
    }
    get(x: number, y: number) {
        if (IsInRect(x, y, this.rect, true)) {
            return this._get(x, y);
        }
    }
    private _get(x: number, y: number) {
        return this[y][x];
    }
    set(x: number, y: number, value: T) {
        if (IsInRect(x, y, this.rect)) {
            this[y][x] = value;
            return true;
        } else {
            return false;
        }
    }
    /** 逐个元素 forEach */
    public $forEach(
        this: Matrix<T>,
        callbackfn: (
            value: T,
            x: number,
            y: number,
            matrix?: Matrix<T>
        ) => void,
        thisArg?: T[][]
    ): void {
        const forEach = Array.prototype.forEach;
        forEach.call(this, (row: T[], y: number) => {
            forEach.call(row, (value: T, x: number) => {
                callbackfn.call(thisArg, value, x, y, this);
            });
        });
    }

    /** 逐个元素 map */
    public $map<T, E>(
        this: Matrix<T>,
        func: (value: T, x: number, y: number, matrix?: Matrix<T>) => E,
        thisArg?: any
    ): T[][] {
        const map = Array.prototype.map;
        return map.call(this, (item: T[], y: number) => {
            return map.call(item, (value: T, x: number) => {
                return func.call(thisArg, value, x, y, this);
            });
        }) as T[][];
    }

    /** 遍历一个二维点周围的位置,包括自身 */
    public $mapAround<Value, Result>(
        this: Matrix<Value>,
        X: number,
        Y: number,
        func: (value: Value, x: number, y: number, matrix?: any) => Result,
        OutOfRange?: (x: number, y: number) => void,
        deep = 1
    ): Result[][] {
        let collection = [];
        for (let x = X - deep; x <= X + deep; x++) {
            const row: Result[] = [];
            collection.push(row);

            for (let y = Y - deep; y <= Y + deep; y++) {
                const isIn = IsInRect(x, y, this.rect);
                if (isIn) {
                    const result = func.call(this, this[y][x], x, y, this);
                    row.push(result);
                } else if (typeof OutOfRange === "function") {
                    OutOfRange(x, y);
                }
            }
        }

        return collection;
    }
    /** 十字遍历, 包括中心， 函数返回 false 会直接停止 */
    public $mapCross<Value>(
        this: Matrix<Value>,
        X: number,
        Y: number,
        func: (
            value: Value,
            x: number,
            y: number,
            matrix?: any
        ) => void | boolean,
        OutOfRange?: (x: number, y: number) => void,
        deep = 1
    ) {
        for (let x = X - deep; x <= X + deep; x++) {
            const isIn = IsInRect(x, Y, this.rect);
            if (isIn) {
                const result = func.call(this, this[Y][x], x, Y, this);
                if (result === false) return;
            } else if (typeof OutOfRange === "function") {
                OutOfRange(x, Y);
            }
        }
        for (let y = Y - deep; y <= Y + deep; y++) {
            const isIn = IsInRect(X, y, this.rect);
            if (isIn) {
                const result = func.call(this, this[y][X], X, y, this);
                if (result === false) return;
            } else if (typeof OutOfRange === "function") {
                OutOfRange(X, y);
            }
        }
    }
}
